#include "Hash.h"

Hash::Hash(HashType type) 
{
    _ctx = EVP_MD_CTX_new();
    if (!_ctx) throw std::runtime_error("Failed to create EVP_MD_CTX");

    const EVP_MD* md = _GetDigest(type);
    if (!EVP_DigestInit_ex(_ctx, md, nullptr))
        throw std::runtime_error("DigestInit failed");
}

Hash::~Hash() 
{
    if (_ctx) EVP_MD_CTX_free(_ctx);
}

int Hash::Update(const void* data, size_t len) 
{
    if (!EVP_DigestUpdate(_ctx, data, len))
    {
        std::cerr << "DigestUpdate failed" << std::endl;
        return -1;
    }
    return 0;
}

std::string Hash::Final() {
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int len = 0;

    if (!EVP_DigestFinal_ex(_ctx, hash, &len))
    {
        return "";
    }
    return ToHex(hash, len);
}

std::string Hash::GetHash(HashType type, const unsigned char* data, size_t len) 
{
    EVP_MD_CTX* tmp_ctx = EVP_MD_CTX_new();
    if (!tmp_ctx) throw std::runtime_error("Failed to create EVP_MD_CTX");

    const EVP_MD* md = _GetDigest(type);
    if (!EVP_DigestInit_ex(tmp_ctx, md, nullptr))
    {
        EVP_MD_CTX_free(tmp_ctx);
        return "";
    }
    if (!EVP_DigestUpdate(tmp_ctx, data, len))
    {
        EVP_MD_CTX_free(tmp_ctx);
        return "";
    }

    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int hash_len = 0;
    if (!EVP_DigestFinal_ex(tmp_ctx, hash, &hash_len))
    {
        EVP_MD_CTX_free(tmp_ctx);
        return "";
    }

    EVP_MD_CTX_free(tmp_ctx);
    return ToHex(hash, hash_len);
}

std::string Hash::ToHex(const unsigned char* data, size_t len) 
{
    std::ostringstream oss;
    for (size_t i = 0; i < len; ++i)
        oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(data[i]);
    return oss.str();
}


const EVP_MD* Hash::_GetDigest(HashType type) 
{
    switch (type) 
    {
        case SHA224_TYPE: return EVP_sha224();
        case SHA256_TYPE: return EVP_sha256();
        case SHA384_TYPE: return EVP_sha384();
        case SHA512_TYPE: return EVP_sha512();
        default: throw std::invalid_argument("Invalid hash type");
    }
}